package owg.util.opengl;

import java.awt.Font;
import java.awt.Point;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.util.HashMap;

import com.jogamp.opengl.GLContext;

import owg.util.euclidian.Compass;
import owg.util.euclidian.MatrixStack;
import owg.util.opengl.jogamp.EGLTextRenderer;
import owg.util.opengl.jogamp.TextRasterizer;

public class TTFFontRenderer implements GLFont
{
	private static final FontRenderContext NULL_FRC = new FontRenderContext(new AffineTransform(), false, false);
	public static File FONT_DIRECTORY = new File("res/fonts/");
	private static HashMap<String, Font> fonts = new HashMap<String, Font>();
	
	public final float lineSeparation;
	
	private final Point offset;
	private final Font font;
	private TextRasterizer rasterizer;
	private ColorProducer colorP;
	
	public TTFFontRenderer(Font font, ColorF color)
	{
		this(font, color, 0, (int) -4);
	}
	public TTFFontRenderer(Font font, ColorF color, int xOffset, int yOffset)
	{
		this(font, 1.5f, color, xOffset, yOffset);
	}
	public TTFFontRenderer(Font font, float lineSeparation, ColorF color)
	{		
		this(font, lineSeparation, color, 0, -4);
	}
	public TTFFontRenderer(Font font, float lineSeparation, ColorF color, int xOffset, int yOffset)
	{		
		this.offset = new Point(xOffset, yOffset);
		this.font = font;
		this.colorP = new PlainColor(color);
		this.lineSeparation = lineSeparation;
	}
	
	public TTFFontRenderer(Font font, int style, int size, float lineSeparation, ColorF color)
	{
		this(font, style, size, lineSeparation, color, 0, -4);
	}
	
	public TTFFontRenderer(Font font, int style, int size, float lineSeparation, ColorF color, int xOffset, int yOffset)
	{
		this(font.deriveFont(style, size), lineSeparation, color, xOffset, yOffset);
	}
	@Override
	public void setColorProducer(ColorProducer colorP)
	{
		this.colorP = colorP;
		if(rasterizer != null)
			rasterizer.setColorProducer(colorP);
	}
	/**Locates the EGL for the current thread and calls begin() with the appropriate value.*/
	public void begin()
	{
		begin(EGL.getEGL(GLContext.getCurrentGL()));
	}
	/**
	 * Begins text rendering. 
	 * Must be called before calls to methods starting with "draw", but not methods starting with "render"! 
	 * This will push the projection and modelview matrices.
	 */
	public void begin(EGL<?> egl)
	{
		egl.projectionMatrix().push();
		MatrixStack um = egl.modelviewMatrix(); 
		um.push();
		
		if(rasterizer == null)
		{
			rasterizer = new EGLTextRenderer(egl, font);
			rasterizer.setSmoothing(false);
			rasterizer.setColorProducer(colorP);
		}
		rasterizer.begin3DRendering();

		//Note: If the number of characters drawn to the screen is greater than approximately 80,
		//then the result is drawn to the screen before end() is called. For this reason, the scaling function must be here.
		//Wat.
		um.scalef(1, -1, 1);
		um.translatef(offset.x, -offset.y, 0);
	}
	/**
	 * Finishes text rendering. 
	 * Must be called after calls to methods starting with "draw", but not methods starting with "render"!
	 * This will pop the projection and modelview matrices.
	 */
	public void end()
	{	
		rasterizer.end3DRendering();
		EGL<?> egl = rasterizer.getEGL();
		egl.modelviewMatrix().pop();
		egl.projectionMatrix().pop();
	}
	@Override
	public void renderConstrained(EGL<?> egl, String text, int x, int y,
		Compass orientation, int maxWidth)
	{
		begin(egl);
		drawConstrained(text, x, y, orientation, maxWidth);
		end();
	}
	@Override
	public void render(EGL<?> egl, String text, int x, int y, Compass orientation)
	{
		begin(egl);
		draw(text, x, y, orientation);
		end();
	}

	@Override
	public void render(EGL<?> egl, String[] text, int x, int y,
		Compass orientation)
	{
		begin(egl);
		draw(text, x, y, orientation);
		end();
	}
	public void draw(String string, float x, float y)
	{
		draw(string, x, y, Compass.NORTHWEST);
	}
	public void draw(String string, float x, float y, Compass orientation)
	{
		draw(string.split("\n", -1), x, y, orientation);
	}
	public void drawConstrained(String text, int x, int y,
		Compass orientation, int maxWidth)
	{
		String[] lines = text.split("\n", -1);
		for(int i = 0; i<lines.length; i++)
		{
			if(getStringWidth(lines) >= maxWidth)
			{
				final String appendix = "...";
				if(getStringWidth(appendix) >= maxWidth)
					lines[i] = "";//No space for anything useful
				else
				{
					StringBuilder prefix = new StringBuilder();
					final String line = lines[i];
					//Could be optimized with binary search
					for(int len = 0; len<line.length(); len++)
					{
						prefix.append(line.charAt(len));
						if (getStringWidth(prefix+appendix) >= maxWidth)
						{
							prefix.setLength(prefix.length()-1);
							break;
						}
					}
					lines[i] = prefix.toString()+appendix; 
				}
			}
		}				
		draw(lines, x, y, orientation);
	}
	public void draw(String[] strings, float x, float y, Compass orientation)
	{	
		float xOff;
		float yOff = -getStringHeight(strings.length)*(orientation.dy);
		for(int i = 0; i<strings.length; i++)
		{
			xOff = -getStringWidth(strings[i])*orientation.dx;
			rasterizer.draw3D(strings[i], i, (int)(x+xOff), (int)(-y-yOff-getLineHeight()*(i+1)), 0, 1);
		}
	}
	public float getStringHeight(String string)
	{
		return getStringHeight(string.split("\n", -1).length);
	}
	
	public float getStringHeight(int lines)
	{
		return lines*getLineHeight();
	}
	
	public float getLineHeight()
	{
		return (int) (getCharWidth('m')*lineSeparation);
	}
	
	public float getStringWidth(String string)
	{
		return getStringWidth(string.split("\n", -1));
	}
	
	public float getStringWidth(String[] strings)
	{
		int maxWidth = 0;
		for(int i = 0; i<strings.length; i++)
		{
			float candWidth;
			if(strings[i].isEmpty())
				candWidth = 0;
			candWidth = (float)font.getStringBounds(strings[i], NULL_FRC).getWidth();
			// Note: GL rasterizer excludes whitespace, which is terrible. It also doesn't work when GL Context is not created.
			//candWidth = (float)rasterizer.getBounds(strings[i]).getWidth();
			maxWidth = (int)Math.max(maxWidth, Math.ceil(candWidth));
		}
		return maxWidth;
	}
	
	@Override
	public int getSize()
	{
		return font.getSize();
	}
	
	public float getCharWidth(char c)
	{
		if(rasterizer == null)
			return (float)font.getStringBounds(String.valueOf(c), NULL_FRC).getWidth();
		else
			return rasterizer.getCharWidth(c);
	}
	
	public static synchronized Font getFont(String ttfFileName)
	{
		if(fonts.containsKey(ttfFileName))
			return fonts.get(ttfFileName);
		else
		{
			try {
				Font font = Font.createFont(Font.TRUETYPE_FONT, new File(FONT_DIRECTORY,ttfFileName+".ttf"));
				fonts.put(ttfFileName, font);
				return font;
			}
			catch(Exception e)
			{
				e.printStackTrace();
				return null;
			}
		}
	}
	
}
